package fregat.base.util;

import java.lang.reflect.Constructor;
import java.util.*;

/**
 * Class that supports working with a configuration file based on resource
 * bundles using keys for configuration.
 *
 * @author Alexander Mann <br><b><i>ECO_L O G</i> &nbsp Logistiksysteme GmbH</b>
 * @version 1.0
 */
public class ConfigurationBundle {
  private static ResourceBundle resource = ResourceBundle.getBundle("at.ecolog.base.util.resource");

  /**
   * Constant for use with <code>getConfigurationEnumeration</code> to indicate
   * configuration array is text
   */
  public static final int STRING = 1;

  /**
   * Constant for use with <code>getConfigurationEnumeration</code> to indicate
   * that configuration array is numerical
   */
  public static final int INTEGER = 2;

  /**
   * resource bundle of configuration
   */
  protected ResourceBundle bundle;

  /**
   * Reads the configuration file into the resource bundle.
   *
   * @param file    Name of file without extension '.properties'
   * @param logging True, if logger(s) should be configured and started
   * @throws MissingResourceException
   */
  public ConfigurationBundle(String file, boolean logging) throws MissingResourceException {
    try {
      bundle = ResourceBundle.getBundle(file);

      // configure logging categories
      if (logging) {
        Enumeration categories = getConfigurationEnumeration("logger.config", ",", STRING);
        while (categories.hasMoreElements()) {
          configureLogger((String) categories.nextElement());
        }
      }

    } catch (MissingResourceException ex) {
      String debug = resource.
          getString("ConfigurationBundle_constructor_1").
          replaceFirst("%1", file);
      System.err.println(debug);
      throw ex;
    }
  }

  /**
   * Reads the configuration file into the resource bundle and starts the
   * configured logger(s).
   *
   * @param file Name of file without extension '.properties'
   * @throws MissingResourceException
   */
  public ConfigurationBundle(String file) throws MissingResourceException {
    this(file, true);
  }

  /**
   * Default constructor
   *
   * @deprecated Implemented for compatibility to prior implementations
   */
  public ConfigurationBundle() {
  }

  /**
   * Configures logger.
   * @param category Category name
   */
  public void configureLogger(String category) {
    configureLogger("logger",category);
  }

  /**
   * Configures logger.
   * @param configPath Path to configuration key
   * @param category Category name
   */
  public void configureLogger(String configPath, String category) {
    // logging -->
    String[] levels = Logger.levels;
    StringBuffer buffer =
        new StringBuffer(resource.getString("ConfigurationBundle_configureLogger_1") +
        " '" + category + "' ");

    String prefix = configPath + "." + category;

    // log level
    int level = getConfigurationInteger(prefix + ".level");
    int consoleLevel = getConfigurationInteger(prefix + ".console.level");
    String file = getConfigurationString(prefix+".file");

    // todo remove this - check is for deprecated configuration files only
    if (level < 0 && consoleLevel < 0 && file == null) {
      prefix = category + ".logger";
      level = getConfigurationInteger(prefix + ".level");
      consoleLevel = getConfigurationInteger(prefix + ".console.level");
    }
    // <--

    if (file != null) {
      // log to file
      long size = getConfigurationInteger(prefix + ".filesize");
      if (size < 0) size = 4000000; // 4 MB as default
      int count = getConfigurationInteger(prefix + ".filecount");
      if (count < 0) count = 4; // 4 files as default
      Logger.getCategory(category).logToFile(file, size, count);
      buffer.append(resource.getString("ConfigurationBundle_configureLogger_3") +
          " '" + file + "'(" + count + " x " + size / 1000000 + " MB)");
    } else {
      Logger.getCategory(category).logToConsole(true);
      Logger.getCategory(category).setConsoleLevel(level);

      buffer.append(resource.getString("ConfigurationBundle_configureLogger_2"));
    }

    if (level < 0) level = 2; // level INFO as default
    Logger.getCategory(category).setLevel(level);
    buffer.append(" " +
        resource.getString("ConfigurationBundle_configureLogger_4") +
        " '" + levels[level] + "'(" + level + ")");

    // log to console
    if (getConfigurationBoolean(prefix + ".console")) {
      if (consoleLevel < 0) consoleLevel = 2; // level INFO as default
      Logger.getCategory(category).logToConsole(true);
      Logger.getCategory(category).setConsoleLevel(consoleLevel);
      buffer.append(" " +
          resource.getString("ConfigurationBundle_configureLogger_5") +
          " '" + levels[consoleLevel] + "'(" + consoleLevel + ")");
    }

    // hide category, level name, timestamp in output
    Logger.getCategory(category).hide(
        getConfigurationBoolean(prefix + ".level.hide"),
        getConfigurationBoolean(prefix + ".category.hide"),
        getConfigurationBoolean(prefix + ".timestamp.hide"));

    // log stack trace with logger (default forwarded to standard error)
    Logger.getCategory(category).logStackTrace(getConfigurationBoolean(prefix+".logStackTrace"));

    System.out.println(buffer);
    // <--
  }

  /**
   * Reads a string from configuration file. Removes all leading and trailing
   * whitespaces of the string.
   *
   * @param key Configuration key
   * @return Configured text or <b>null</b> if none
   */
  public String getConfigurationString(String key) {
    try {
      return bundle.getString(key).trim();
    } catch (MissingResourceException ex) {
      String debug = resource.
          getString("ConfigurationBundle_getConfigurationString_1").
          replaceFirst("%1", key);
      System.out.println(debug);
    }
    return null;
  }

  /**
   * Reads a boolean from configuration file. If not configured all boolean
   * values are assumed as false.
   *
   * @param key Configuration key
   * @return Configurated boolean value or <b>false</b> if none
   */
  public boolean getConfigurationBoolean(String key) {
    try {
      return new Boolean(getConfigurationString(key)).booleanValue();
    } catch (NullPointerException ex) {
    } catch (Exception ex) {
      String debug = resource.
          getString("ConfigurationBundle_getConfigurationBoolean_1").
          replaceFirst("%1", key);
      System.err.println(debug);
    }
    return false;
  }

  /**
   * Reads an integer from configuration file.
   *
   * @param key Configuration key
   * @param logError Log errors
   * @return Configured integer or <b>-1</b> if none
   */
  public int getConfigurationInteger(String key, boolean logError) {
    try {
      String value = getConfigurationString(key);
      if (value != null) {
        return Integer.parseInt(value);
      }
    } catch (NumberFormatException ex) {
      if(logError) {
        String debug = resource.
            getString("ConfigurationBundle_getConfigurationInteger_1").
            replaceFirst("%1", key);
        System.err.println(debug);
      }
    }
    return -1;
  }

   /**
   * Reads an integer from configuration file.
   *
   * @param key Configuration key
   * @return Configured integer or <b>-1</b> if none
   */
  public int getConfigurationInteger(String key) {
    return getConfigurationInteger(key, true);
  }

  /**
   * Reads a long from configuration file.
   *
   * @param key Configuration key
   * @return Configured long or <b>-1</b> if none
   */
  public long getConfigurationLong(String key) {
    try {
      String value = getConfigurationString(key);
      if (value != null) {
        return Long.parseLong(value);
      }
    } catch (NumberFormatException ex) {
      String debug = resource.
          getString("ConfigurationBundle_getConfigurationLong_1").
          replaceFirst("%1", key);
      System.err.println(debug);
    }
    return -1;
  }

  /**
   * Reads an array of values for the given key from the configuration file and
   * returns an <code>Enumeration</code> of objects as specified by <code>type</code>.
   *
   * @param key       Configuration key
   * @param separator Separation characters
   * @param type      Type of configuration values (use finals <code>STRING,INTEGER</code>)
   * @return Enumeration of configured values, empty enumeration if no values
   */
  public Enumeration getConfigurationEnumeration(String key, String separator, int type) {
    String value = getConfigurationString(key);
    Vector values;

    if (value != null) {
      StringTokenizer tokenizer = new StringTokenizer(value, separator);
      values = new Vector(tokenizer.countTokens());

      while (tokenizer.hasMoreTokens()) {
        switch (type) {
          case INTEGER:
            try {
              values.addElement(new Integer(tokenizer.nextToken().trim()));
            } catch (NumberFormatException ex) {
              String debug = resource.
                  getString("ConfigurationBundle_getConfigurationEnumeration_1").
                  replaceFirst("%1", key);
              System.err.println(debug);
            }
            break;
          case STRING:
          default:
            values.addElement(tokenizer.nextToken().trim());
        }
      }
    } else {
      values = new Vector(0);
    }

    return values.elements();
  }

  /**
   * Uses the given objects class for constructor class type.
   *
   * @param configPath Configuration path
   * @param o          Additional instance to be added at the end of configured parameters
   * @return Created object
   * @see {@link Object createObject(java.lang.String,java.lang.Object,java.lang.String)}
   */
  public Object createObject(String configPath, Object o) {
    String strObjectClass = null;
    if (o != null) {
        strObjectClass = o.getClass().getName();
    }
    return createObject(configPath, o, strObjectClass);
  }

  /**
   * Creates an instance(using the reflection API) for the class that is
   * specified in the configuration file with the configuration keys:<br>
   * &lt;configPath&gt;.class contains full qualified class name<br>
   * &lt;configPath&gt;.parameter contains integer constructor parameters
   * seperated with commas<br>
   *
   * @param configPath  Configuration path
   * @param o           Additional instance to be added at the end of configured parameters
   * @param objectClass Class name of object (necessary if using abstract super classes for constructor)
   * @return Created object or <b>null</b> if errors
   */
  public Object createObject(String configPath, Object o, String objectClass) {
    System.out.print(resource.getString("ConfigurationBundle_createObject_1") + " " + configPath);
    String className = bundle.getString(configPath + ".class");
    int intParameterCount = 0;
    int intObjectCount = 0;
    StringBuffer buffer = new StringBuffer(" = new " + className + "(");

    if (o != null) {
      intObjectCount++;
    }

    StringTokenizer parameterTokenizer = null;
    try {
      parameterTokenizer =
          new StringTokenizer(bundle.getString(configPath + ".parameter").trim(), ",");
      intParameterCount += parameterTokenizer.countTokens();
    } catch (MissingResourceException ex) {
    }

    Object parameterValues[] = new Object[intParameterCount + intObjectCount];
    Class parameterClasses[] = new Class[intParameterCount + intObjectCount];

    for (int i = 0; intParameterCount > 0 && parameterTokenizer.hasMoreTokens(); i++) {
      parameterValues[i] = new Integer(parameterTokenizer.nextToken().trim());
      parameterClasses[i] = int.class;
      buffer.append(parameterValues[i]);
      if (i < intParameterCount - 1) {
          buffer.append(',');
      }
    }

    if (o != null) {
      parameterValues[intParameterCount] = o;
      try {
        parameterClasses[intParameterCount] = Class.forName(objectClass);
      } catch (ClassNotFoundException ex) {
        ex.printStackTrace();
      }
      if (intParameterCount >= 1) {
          buffer.append(",");
      }
      buffer.append(o.getClass().getName());
    }
    buffer.append(") ...");

    System.out.println(buffer);

    try {
      Class createClass = Class.forName(className);
      Constructor constructor = createClass.getConstructor(parameterClasses);
      Object instance = constructor.newInstance(parameterValues);
      System.out.println("... ok");
      return instance;
    } catch (Exception ex) {
      ex.printStackTrace();
    }
    return null;
  }
}
